package testing;

import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.Iterator;
import java.util.Random;


public class DecodingImplementation implements DecodingMethods {
	ArrayList<Integer> index = new ArrayList<Integer>();

	@SuppressWarnings("unchecked")
	@Override
	/*
	 * G - generator matrix. k - number of source fragments , N_new- number of
	 * encoded fragments
	 */
	public ArrayList<BitSet> decodePackets(ArrayList<BitSet> encodedFragments,

	int[][] G, int k, int N_new) {
		ArrayList<BitSet> encodedFragmentsNew = (ArrayList<BitSet>) encodedFragments
				.clone();
		// System.out.println(" original matrix");
		// printMatrix(G, k, N_new);
		int n = k;
		int N = N_new;
		for (int p = 0; p < n; p++) {
			int max = p;
			for (int i = p + 1; i < N; i++) {
				if (Math.abs(G[i][p]) > Math.abs(G[max][p])) {
					max = i;
				}
			}
			int[] temp = G[p];
			G[p] = G[max];
			G[max] = temp;

			BitSet t = encodedFragmentsNew.get(p);
			encodedFragmentsNew.set(p, encodedFragmentsNew.get(max));
			encodedFragmentsNew.set(max, t);
			t = null;
			// System.out.println(" diagonal matrix");
			// printMatrix(G, k, N_new);

			if (Math.abs(G[p][p]) != 1) {
				throw new RuntimeException(
						"Matrix is singular or nearly singular");
			}

			for (int i = p + 1; i < N; i++) {
				int alpha = -(G[i][p] / G[p][p]);
				int index = i;
				BitSet value = encodedFragmentsNew.get(index);
				if (alpha == 0) {
					/*
					 * change all elements of this.encodedFragments.get(p)=0
					 */
					// this.encodedFragments.get(p).clear();
					encodedFragmentsNew.set(index, value);
				} else {
					// e= e.xor(a*G)
					value.xor(encodedFragmentsNew.get(p));
					encodedFragmentsNew.set(index, value);
				}
				value = null;
				for (int j = p; j < n; j++) {
					G[i][j] = G[i][j] ^ (alpha & G[p][j]);
				}
			}
			// System.out.println(" lower triangle is ");
			// printMatrix(G, k, N);

			for (int i = 0; i <= p - 1; i++) {
				int alpha = -(G[i][p] / G[p][p]);
				int index = i;
				BitSet value = encodedFragmentsNew.get(index);
				if (alpha == 0) {
					/*
					 * change all elements of this.encodedFragments.get(p)=0
					 */
					// this.encodedFragments.get(p).clear();
					// this.encodedFragments.remove(p);
					encodedFragmentsNew.set(index, value);
				} else {
					value.xor(encodedFragmentsNew.get(p));
					encodedFragmentsNew.set(index, value);
				}
				value = null;
				for (int j = p; j < n; j++) {
					G[i][j] = G[i][j] ^ (alpha & G[p][j]);
				}
			}
			// System.out.println(" upper triangle");
			// printMatrix(G, k, N_new);
			// System.out
			// .println("variable number is +++++++++++++++++++++++++++++++++++"
			// + p);
			// System.out.println(new String(encodedFragmentsNew.get(p)
			// .toByteArray()));
		}

		ArrayList<BitSet> obtainedSource = new ArrayList<BitSet>();
		for (int i = 0; i < n; i++) {
			if (G[i][i] == 0) {
				System.out.println(" error in the matrix cannot solve");
			} else {
				/*
				 * assign the values of the of the current encoded fragments to
				 * obtained source *
				 */
				obtainedSource.add(encodedFragmentsNew.get(i));
			}
		}

		//
		// ArrayList<Byte> obtainedSource = new ArrayList<Byte>();
		// for (int i = n - 1; i >= 0; i--)
		// {
		// byte[] enByte = this.encodedFragments.get(i).toByteArray();
		// int sum = 0;
		// for (int j = i + 1; j < n; j++)
		// {
		// sum += G[i][j]&x[j];
		// }
		// x[i] = (byte) ((enByte[i] - sum) / G[i][i]);
		// }

		// System.out.println("the values of unknowns are:-");
		// for (int i = 0; i < n; i++)
		// System.out.println("x" + i + "  " +x[i]);

		return obtainedSource;

		// return null;
	}

	/*
	 * input - encoded fragments, generator matrix' , dimensions of original
	 * generator matrix, sourceIndex
	 */
	@SuppressWarnings("null")
	public ArrayList<BitSet> decodePacketsVer2(
			ArrayList<BitSet> encodedFragments, int[][] G, int k, int N_new,
			ArrayList<Integer> sourceIndex) {
		// ArrayList<BitSet> ec = encodedFragments;
		// System.out.println(" original matrix");
		// printMatrix(G, k, N_new);
		// this.encodedFragments= encodedFragments;
		ArrayList<BitSet> encodedFragmentsNew = new ArrayList<BitSet>();
		int[][] newGenerator = new int[k][k];
		int l = 0;
		for (int i : sourceIndex) {
			for (int j = 0; j < k; j++) {
				newGenerator[l][j] = G[i][j];
			}
			encodedFragmentsNew
					.add(l, (BitSet) encodedFragments.get(i).clone());
			l++;
		}
		// printMatrix(newGenerator, k, k);

		int n = k;
		int N = k;
		// ArrayList<String> decodedString = new ArrayList<String>();
		for (int p = 0; p < n; p++) {
			int max = p;
			for (int i = p + 1; i < N; i++) {
				if (Math.abs(newGenerator[i][p]) > Math
						.abs(newGenerator[max][p])) {
					max = i;
				}
			}
			int[] temp = newGenerator[p];
			newGenerator[p] = newGenerator[max];
			newGenerator[max] = temp;

			BitSet t = encodedFragmentsNew.get(p);
			encodedFragmentsNew.set(p, encodedFragmentsNew.get(max));
			encodedFragmentsNew.set(max, t);
			t = null;
			// System.out.println(" diagonal matrix");
			// printMatrix(G, k, N_new);

			if (Math.abs(newGenerator[p][p]) != 1) {
				throw new RuntimeException(
						"Matrix is singular or nearly singular");
			}

			for (int i = p + 1; i < N; i++) {
				int alpha = -(newGenerator[i][p] / newGenerator[p][p]);
				int index = i;
				BitSet value = encodedFragmentsNew.get(index);
				if (alpha == 0) {
					/*
					 * change all elements of this.encodedFragments.get(p)=0
					 */
					// this.encodedFragments.get(p).clear();
					encodedFragmentsNew.set(index, value);
				} else {
					// e= e+ a*G
					value.xor(encodedFragmentsNew.get(p));
					encodedFragmentsNew.set(index, value);
				}
				value = null;
				for (int j = p; j < n; j++) {
					newGenerator[i][j] = newGenerator[i][j]
							^ (alpha & newGenerator[p][j]);
				}
			}
			System.out.println(" lower triangle is decode n the fly");
			printMatrix(newGenerator, k, k);
			for (int i = 0; i <= p - 1; i++) {
				int alpha = -(newGenerator[i][p] / newGenerator[p][p]);
				int index = i;
				BitSet value = encodedFragmentsNew.get(index);
				if (alpha == 0) {
					/*
					 * change all elements of this.encodedFragments.get(p)=0
					 */
					// this.encodedFragments.get(p).clear();
					// this.encodedFragments.remove(p);
					encodedFragmentsNew.set(index, value);
				} else {
					value.xor(encodedFragmentsNew.get(p));
					encodedFragmentsNew.set(index, value);
				}
				value = null;
				for (int j = p; j < n; j++) {
					newGenerator[i][j] = newGenerator[i][j]
							^ (alpha & newGenerator[p][j]);
				}
			}
			// System.out.println(" upper triangle");
			// printMatrix(newGenerator, k, N_new);
			// System.out
			// .println("variable number is +++++++++++++++++++++++++++++++++++"
			// + p);
			// System.out.println(new String(encodedFragmentsNew.get(p)
			// .toByteArray()));
		}

		ArrayList<BitSet> obtainedSource = new ArrayList<BitSet>();
		for (int i = 0; i < n; i++) {
			if (newGenerator[i][i] == 0) {
				System.out.println(" error in the matrix cannot solve");
			} else {
				/*
				 * assign the values of the of the current encoded fragments to
				 * obtained source *
				 */
				obtainedSource.add(encodedFragmentsNew.get(i));
			}
		}

		return obtainedSource;

		// return null;
	}

	void printMatrix(int[][] G, int k, int N_new) {
		for (int i = 0; i < N_new; i++) {
			for (int j = 0; j < k; j++) {
				System.out.print(" " + G[i][j]);
			}
			System.out.println();
		}
	}

	/*
	 * input - transpose of generator matrix , encoded fragments each iteration
	 * before selecting the particular server check if the encoded fragment is
	 * not equal to zero before processing further
	 */

	public ArrayList<Integer> decodeOnTheFly(int[][] b, int k, int N_new,
			ArrayList<BitSet> encodedFrag, int[][] G, int idx) {
		// ArrayList<BitSet> obtainedEncodedFrag = encodedFrag;
		// ArrayList<Integer> sourceIndex = new ArrayList<Integer>();
		int emptyRows;
		int firstExecution = 0;
		int indexForInsertion = 0;
		int i = 0;
		/*
		 * G- initially empty but filled with equations from b as the code flows
		 */
		i = -1;
		if (index.isEmpty()) {
			emptyRows = k;
			firstExecution = 1;

		} else {
			// TODO how to debug the case the first server itself is not
			// available ?
			emptyRows = 1;
			indexForInsertion = idx;
//			i=index.get(k-1);
			Object max= Collections.max(index);
			i=(Integer) max;
		}
		int[] eqn = new int[k];
		int numOfOnes = 0;
		int leftMost = 0;
		int leftMostPos = 0;
		// int i = -1;
		// for (int i = 0; i < 7; i++) {
		// emptyRows = k;
		while (emptyRows > 0 && i <= N_new - 2) {
			i++;
			// System.out.println(" length of encoded frag"+obtainedEncodedFrag.get(i).length());
			// if (obtainedEncodedFrag.get(i).length() != 0) {
			numOfOnes = 0;
			leftMost = 0;
			leftMostPos = 0;
			/*
			 * eqn = extract one row from the generator matrix
			 */
			for (int j = 0; j < k; j++) {
				eqn[j] = b[i][j];
				if (eqn[j] == 1) {
					numOfOnes++;
					if (leftMost == 0) {
						leftMost = 1;
						leftMostPos = j;
					}
				}
			}
			/*
			 * calculate the degree of the row in G and the diagonal element
			 */
			int gDegree = 0;
			int gDiag = 0;
			for (int l = 0; l < k; l++) {
				if (G[leftMostPos][l] == 1) {
					gDegree++;
				}
			}
			if (G[leftMostPos][leftMostPos] == 1) {
				gDiag = 1;
			}
			while (gDiag == 1 && numOfOnes > 0) {
				if (numOfOnes >= gDegree) {
					/*
					 * eqn'=xor G[s] and eqn
					 */
					for (int l = 0; l < k; l++) {
						eqn[l] = (eqn[l] + G[leftMostPos][l]) % 2;
					}
				} else {
					/*
					 * swap eqn and G[s]
					 */
					int temp;
					for (int l = 0; l < k; l++) {
						temp = eqn[l];
						eqn[l] = G[leftMostPos][l];
						G[leftMostPos][l] = temp;
					}
				}
				/*
				 * calculate left most 1 in eqn and degree of eqn'( numOfOnes)
				 */
				leftMost = 0;
				leftMostPos = 0;
				numOfOnes = 0;
				int l = 0;
				while (l < k) {
					if (eqn[l] == 1) {
						if (leftMost == 0) {
							leftMostPos = l;
							leftMost++;
						}
						numOfOnes++;
					}
					l++;
				}
				gDegree = 0;
				for (l = 0; l < k; l++) {
					if (G[leftMostPos][l] == 1) {
						gDegree++;
					}
				}
				gDiag = 0;
				if (G[leftMostPos][leftMostPos] == 1) {
					gDiag = 1;
				}

			}
			if (numOfOnes > 0) {
				/*
				 * add eqn to G[s], gDegree=numOfOnes, emptyRows=emptyRows-1
				 */
				if (firstExecution == 1) {
					emptyRows = emptyRows - 1;
					for (int m = 0; m < k; m++) {
						G[leftMostPos][m] = eqn[m];
					}
					gDegree = numOfOnes;
					index.add(i);
				} else {
					if (!index.contains(i)) {
						for (int m = 0; m < k; m++) {
							G[leftMostPos][m] = eqn[m];
						}
						gDegree = numOfOnes;
						emptyRows = emptyRows - 1;
						index.remove(indexForInsertion);
						index.add(indexForInsertion, i);
						// indexForInsertion++;
					}
				}

			} else {
				/*
				 * discard eqn
				 */
			}

			// }
		}
		return index;

	}

	public int[] serverAvailability(int numberOfServers) {
		int[] servers = new int[numberOfServers];
		Random random = new Random();
		for (int i = 0; i < numberOfServers; i++) {
			if (random.nextDouble() < 0.9) {
				servers[i] = 1;
			} else {
				servers[i] = 0;
			}
			System.out.print(" " + servers[i]);
		}
		return servers;

	}

	public ArrayList<Integer> callDecodeOnTheFly(int[][] b, int k, int N_new,
			ArrayList<BitSet> encodedFrag) {
		int[][] G = new int[k][k];
		int[] servers = serverAvailability(N_new);
		// int[][] newGenerator;
		int created = 0;
		int idx = 0;
		int shouldExecute = 1;
		System.out.println();
		while (created < k) {
			if (shouldExecute == 1) {
				index = decodeOnTheFly(b, k, N_new, encodedFrag, G, idx);
				shouldExecute = 0;
			}
			Iterator<Integer> iter = index.iterator();
			while (iter.hasNext()) {
				int serverNumber = iter.next();
				System.out.print("\n" + serverNumber + ":"
						+ servers[serverNumber]);
				// TODO set the last row in the G matrix to 0 and set value of i
				// in the above matrix to be the serverNumber do not delete the
				// entry here from the arraylist
				// and then try to find the corresponding server which has
				// linearly independent value.
				// last row because the entry will be automatically linealry
				// independent with all other rows.
				if (servers[serverNumber] == 0) {
					for (int j = 0; j < k; j++) {
						b[serverNumber][j] = 0;
					}
					idx = index.indexOf(serverNumber);
					// index.remove(idx);
					// index.add(idx, 0);

					for (int j = 0; j < k; j++) {
						G[k - 1][j] = 0;
					}

					// index.set(serverNumber);
					// index = null;
					created = 0;
					shouldExecute = 1;
					break;
				} else {
					created++;
				}
			}
		}
		System.out.println();
		return index;
	}
}
